package org.example.lotterysystem.service.mq;


import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.example.lotterysystem.common.exception.ServiceException;
import org.example.lotterysystem.common.utils.JacksonUtil;
import org.example.lotterysystem.common.utils.MailUtil;
import org.example.lotterysystem.common.utils.SMSUtil;
import org.example.lotterysystem.controller.param.DrawPrizeParam;
import org.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import org.example.lotterysystem.dao.dataobject.WinningRecordDO;
import org.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import org.example.lotterysystem.dao.mapper.WinningRecordMapper;
import org.example.lotterysystem.service.DrawPrizeService;
import org.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import org.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import org.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import org.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import org.example.lotterysystem.service.enums.ActivityStatusEnum;
import org.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Slf4j
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) {
        // 成功接收到队列中的消息
        log.info("MQ 成功接收到消息，message:{}", JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        // 处理抽奖流程
        try {

//            if (true) {
//                return;
//            }

            // 校验抽奖请求是否有效
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }


            // 状态扭转处理 !!!  (策略 + 责任链模式)
            statusConvert(param);

            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者 (邮箱、短信)
            // 抽奖之后的后续流程，异步并发处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            log.info("处理 MQ 消息异常！{}:{}", e.getCode(), e.getMessage(), e);
            // 需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常: 消息重试，解决异常
            throw e;

        } catch (Exception e) {
            log.info("处理 MQ 消息异常！", e);
            // 需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常
            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚行为: 回复请求之前的库表状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1. 回滚状态：活动、奖品、人员
        if (!statusNeedRollback(param)) {
            return;
        }
        rollbackStatus(param);

        // 2. 回滚中奖者名单
        if (!winnerNeedRollback(param)) {
            return;
        }
        rollbackWinner(param);
    }

    /**
     * 判断 回滚状态：活动、奖品、人员 是否需要回滚
     *
     * @param param
     * @return
     */
    private boolean statusNeedRollback(DrawPrizeParam param) {
        // 回滚 活动 + 人员 + 奖品
        // 扭转状态时，保证了事务一致性，要么都扭转，要么都没有扭转
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name());
    }

    /**
     * 回滚 回滚状态：活动、奖品、人员
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner::getUserId).toList()
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);


        activityStatusManager.rollbackHandleEvent(convertActivityStatusDTO);
    }

    /**
     * 判断 中奖者名单 是否需要回滚
     *
     * @param param
     * @return
     */
    private boolean winnerNeedRollback(DrawPrizeParam param) {
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚 中奖者名单
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池 ThreadPoolTaskExecutor

        // 短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));
        // 邮件通知
        threadPoolTaskExecutor.execute(() -> sendMail(winningRecordDOList));

    }

    /**
     * 发短信
     *
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空！不用发短信");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_478985282",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 发邮件
     *
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空！不用发邮件");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(), "中奖通知", context);
        }
    }


    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .toList()
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handleEvent(convertActivityStatusDTO);
    }
}
